package com.jack.payment.controller.admin.auth;

import com.j256.twofactorauth.TimeBasedOneTimePasswordUtil;
import com.jack.payment.constant.ResultHintConstant;
import com.jack.payment.constant.enums.AccountVerifyCodeEnum;
import com.jack.payment.constant.enums.LoginMethodEnum;
import com.jack.payment.entity.common.BusinessException;
import com.jack.payment.entity.common.CommonAskParam;
import com.jack.payment.entity.common.CommonResult;
import com.jack.payment.entity.generate.user.UserApiModel;
import com.jack.payment.entity.generate.user.UserAuthModel;
import com.jack.payment.entity.generate.user.UserDeviceModel;
import com.jack.payment.entity.generate.user.UserModel;
import com.jack.payment.entity.request.BasicAccountModel;
import com.jack.payment.entity.request.OneFieldModel;
import com.jack.payment.entity.result.LoginSuccessModel;
import com.jack.payment.service.basic.impl.UserServiceImpl;
import com.jack.payment.service.basic.impl.UserSettingServiceImpl;
import com.jack.payment.token.util.TokenUtil;
import com.jack.payment.util.auth.AuthUtils;
import com.jack.payment.util.common.StringUtil;
import com.jack.payment.util.common.TimeUtil;
import com.jack.payment.util.generate.UserGenerate;
import com.jack.payment.util.redis.RedisUtil;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;

import java.security.GeneralSecurityException;

@Controller
@Tag(name = "用户基础设置")
@RequestMapping("/api/v1/userSetting")
public class UserSettingController {
    private Logger log = LoggerFactory.getLogger(UserSettingController.class);
    private final static Integer SECRET_LENGTH = 16;
    private final static String SUFFIX_PROJECT_NAME = "JackPay";

    @Autowired
    private UserSettingServiceImpl userSettingService;
    @Autowired
    private TokenUtil tokenUtil;
    @Autowired
    private RedisUtil redisUtil;
    @Autowired
    private UserGenerate userGenerate;
    @Autowired
    private UserServiceImpl userService;


    @Operation(summary = "生成新的密钥")
    @RequestMapping(value = "createNewsApiKey", method = {RequestMethod.POST})
    @ResponseBody
    public CommonResult createApiKey(@RequestBody @Validated CommonAskParam<OneFieldModel.ApiNameModel> data) throws BusinessException {
        UserModel getUserModel = (UserModel) data.getCommon().getUserId();
        OneFieldModel.ApiNameModel newAPIKey = data.getData();
        String apiKey = userSettingService.generateNewUserApiKey();
        userSettingService.generateNewApiKeyRecord(
                userGenerate.generateNewUserApi(getUserModel.getId(), apiKey, newAPIKey.getName(), 1)
        );
        return CommonResult.ok();
    }

    @Operation(summary = "删除apiKey")
    @RequestMapping(value = "deleteApiKey", method = {RequestMethod.POST})
    @ResponseBody
    public CommonResult deleteUserApiKey(@RequestBody @Validated CommonAskParam<OneFieldModel> data) throws BusinessException {
        UserModel getUserModel = (UserModel) data.getCommon().getUserId();
        String id = data.getData().getField();
        UserApiModel userApiModel = userSettingService.getAppointIdApiKey(id);
        if (userApiModel.getUser().equals(getUserModel.getId())) {
            UserApiModel updateUserApi = new UserApiModel();
            updateUserApi.setId(id);
            updateUserApi.setEnable(0);
            updateUserApi.setDeleteTime(TimeUtil.getCurrentTime());
            userSettingService.updateUserApiModel(updateUserApi);
        }
        return CommonResult.ok();
    }

    @Operation(summary = "")
    @RequestMapping(value = "unbindUserDeviceStatus", method = {RequestMethod.POST})
    @ResponseBody
    public CommonResult unbindUserDeviceStatus(@RequestBody @Validated CommonAskParam<OneFieldModel> data) throws BusinessException {
        UserModel getUserModel = (UserModel) data.getCommon().getUserId();
        String deviceId = data.getData().getField();
        userSettingService.unbindUserDeviceStatus(
                new UserDeviceModel().builder()
                        .enable(0)
                        .userId(getUserModel.getId())
                        .id(deviceId)
                        .build());
        return CommonResult.ok();
    }


    @Operation(summary = "更换2FA 或 重新设置 密钥")
    @RequestMapping(value = "update2FAVerifyMethod", method = {RequestMethod.POST})
    @ResponseBody
    public CommonResult update2FAVerifyMethod(@RequestBody @Validated CommonAskParam data) throws BusinessException{
        UserModel getUserModel = (UserModel) data.getCommon().getUserId();
        String base32Secret = TimeBasedOneTimePasswordUtil.generateBase32Secret(SECRET_LENGTH);
        userSettingService.updateNew2FALonginSecret(
                new UserModel().builder()
                        .id(getUserModel.getId())
                        .verifyTwoSecret(base32Secret)
                        .build()
        );
        String accountName = SUFFIX_PROJECT_NAME + getUserModel.getEmail() + "-" + TimeUtil.getSimpleCurrentData();
        return CommonResult.okay(
                200,
                "otpauth://totp/" + accountName + "?secret=" + base32Secret);
    }

    @Operation(summary = "2fa 密钥验证")
    @RequestMapping(value = "verify2FACorrect", method = {RequestMethod.POST})
    @ResponseBody
    public CommonResult verify2FACorrect(@RequestBody CommonAskParam<BasicAccountModel> data) throws BusinessException{
        UserModel getUserModel = (UserModel) data.getCommon().getUserId();
        BasicAccountModel basicAccountModel = data.getData();
        String getTwoSecret = null;
        if (basicAccountModel.getSource() == 1) {
            getTwoSecret = getUserModel.getVerifyTwoSecret();
        } else {
            getTwoSecret = getUserModel.getTwoSecret();
        }
        if (StringUtil.isEmpty(getTwoSecret)) {
            log.warn("该用户{}未设定2FA登录方式", getUserModel.getId());
            throw new BusinessException(ResultHintConstant.LOGIN_2FA_UNBIND);
        }
        String secretCode = null;
        try {
            secretCode = TimeBasedOneTimePasswordUtil.generateCurrentNumberString(getTwoSecret);
        } catch (GeneralSecurityException e) {
            log.error("获取OTP 密码错误, 错误信息如下" + e);
            throw new BusinessException(ResultHintConstant.LOGIN_2FA_ERROR);
        }
        if (!basicAccountModel.getPwd().equals(secretCode)) {
            log.warn("{}账户输入密钥错误,用户输入的安全码是{}, 正确的是{}",
                    basicAccountModel.getAccount(),
                    basicAccountModel.getPwd(),
                    secretCode);
            throw new BusinessException(ResultHintConstant.LOGIN_2FA_VERIFY);
        }
        if (basicAccountModel.getSource() == 1) {
            userSettingService.updateNew2FALonginSecret(new UserModel().builder()
                    .id(getUserModel.getId())
                    .twoSecret(getUserModel.getVerifyTwoSecret())
                    .build());
        }
        return CommonResult.ok();
    }


    @Operation(summary = "用户基础信息更改")
    @RequestMapping(value = "updateUserBasic", method = {RequestMethod.POST})
    @ResponseBody
    public CommonResult updateUserBasic(@RequestBody @Validated CommonAskParam<BasicAccountModel.UpdateUserDetailModel> data) throws BusinessException {
        UserModel getUserModel = (UserModel) data.getCommon().getUserId();
        BasicAccountModel.UpdateUserDetailModel updateUserBasicModel = data.getData();
        userSettingService.updateUserBasicInfo(userGenerate.updateUserBasicInfo(getUserModel.getId(), updateUserBasicModel));
        return CommonResult.ok();
    }

    @Operation(summary = "修改登录密码")
    @RequestMapping(value = "updateUserLoginPwd", method = {RequestMethod.POST})
    @ResponseBody
    public CommonResult updateUserLoginPwd(@RequestBody  CommonAskParam<BasicAccountModel.UpdateLoginPwdModel> data) throws BusinessException{
        UserModel getUserModel = (UserModel) data.getCommon().getUserId();
        BasicAccountModel.UpdateLoginPwdModel pwdModel = data.getData();
        String getOldPwdAuth = AuthUtils.md5Hex(pwdModel.getOldPwd(), getUserModel.getSalt());
        if (!getOldPwdAuth.equals(getUserModel.getPwd())) {
            log.warn("修改登录密码>>>> 旧密码不正确");
            throw new BusinessException();
        }
        userSettingService.updateUserLoginPwd(
                userGenerate.updateUserLoginPwd(getUserModel.getId(), pwdModel.getPwd())
        );
        return CommonResult.ok();
    }

    @Operation(summary = "解除三方登录方式")
    @RequestMapping(value = "unbindAuthLoginMethod", method = {RequestMethod.POST})
    @ResponseBody
    public CommonResult unbindAuthLoginMethod(@RequestBody @Validated CommonAskParam<OneFieldModel> data) throws BusinessException {
        UserModel getUserModel = (UserModel) data.getCommon().getUserId();
        Integer channel = Integer.parseInt(data.getData().getField());
        LoginMethodEnum loginMethodEnum = LoginMethodEnum.getAppointLoginMethod(channel);
        userSettingService.unbindAuthLoginMethod(
                channel,
                userGenerate.updateUserModelThirdAccount(
                        "",
                        new UserModel().builder()
                                .id(getUserModel.getId())
                                .build(),
                        loginMethodEnum
                )
        );
        return CommonResult.ok();
    }

    @Operation(summary = "绑定第三方登录方式")
    @RequestMapping(value = "bindAuthLoginMethod", method = {RequestMethod.POST})
    @ResponseBody
    public CommonResult bindAuthLoginMethod(@RequestBody CommonAskParam<BasicAccountModel.AuthLoginRegisterModel> data) throws BusinessException {
        UserModel getUserModel = (UserModel) data.getCommon().getUserId();
        BasicAccountModel.AuthLoginRegisterModel accountModel = data.getData();
        UserAuthModel getAuthModel = userService.getAppointSecretUserAuth(
                accountModel.getSecret(),
                accountModel.getChannel());
        if (getAuthModel != null) {
            log.warn("该三方账号已被其他人绑定, {}", getAuthModel.getUser());
            throw new BusinessException();
        }
        LoginMethodEnum loginMethodEnum = LoginMethodEnum.getAppointLoginMethod(accountModel.getChannel());

        userSettingService.bindAuthLoginMethod(
                userGenerate.generateUserAuth(
                        getUserModel.getId(),
                        accountModel.getChannel(),
                        accountModel.getSecret(),
                        accountModel.getThirdAccount()),
                userGenerate.updateUserModelThirdAccount(
                        accountModel.getThirdAccount(),
                        new UserModel().builder().id(getUserModel.getId()).build(),
                        loginMethodEnum
                )
        );
        return CommonResult.ok();
    }

    @Operation(summary = "绑定 或 换绑 [手机 或 email]")
    @RequestMapping(value = "bindAccountBasicLoginMethod", method = {RequestMethod.POST})
    @ResponseBody
    public CommonResult bindAccountBasicMethod(@RequestBody CommonAskParam<BasicAccountModel> data) throws BusinessException {
        UserModel getUserModel = (UserModel) data.getCommon().getUserId();
        BasicAccountModel basicAccountModel = data.getData();
        AccountVerifyCodeEnum verifyCodeEnum = AccountVerifyCodeEnum.VERIFY;
        // 验证账号是否合法
        verifyAccountLegal(basicAccountModel, getUserModel);
        LoginMethodEnum loginMethodEnum = LoginMethodEnum.getAppointLoginMethod(basicAccountModel.getSource());
        userSettingService.bindAccountBasicLoginMethod(
                userGenerate.updateUserModelThirdAccount(
                        basicAccountModel.getAccount(),
                        new UserModel().builder().id(getUserModel.getId()).build(),
                        loginMethodEnum
                        )
        );
        return CommonResult.ok();
    }

    @Operation(summary = "退出登录")
    @RequestMapping(value = "userLogout", method = {RequestMethod.POST})
    @ResponseBody
    public CommonResult userLogout(@RequestBody CommonAskParam data) throws BusinessException {
        UserModel getUserModel = (UserModel) data.getCommon().getUserId();
        tokenUtil.logout("admin", getUserModel.getId());
        return CommonResult.ok();
    }

    @Operation(summary = "获取用户基础信息")
    @RequestMapping(value = "getUserBasicDetail", method = {RequestMethod.POST})
    @ResponseBody
    public CommonResult getUserBasicDetail(@RequestBody CommonAskParam data) throws BusinessException {
        UserModel getUserModel = (UserModel) data.getCommon().getUserId();
        return CommonResult.ok(
                new LoginSuccessModel().builder()
                        .userBasic(getUserModel)
                        .userSetting(userService.getAppointUserSettingInfo(getUserModel.getId()))
                        .build());
    }


    /**
     * 验证账户是否合法
     * @param accountModel
     */
    protected void verifyAccountLegal(BasicAccountModel accountModel,
                                      UserModel getUserModel) throws BusinessException {
        switch (accountModel.getSource()) {
            case 0, 1 -> {
                AccountVerifyCodeEnum verifyCodeEnum = AccountVerifyCodeEnum.VERIFY;
                String redisKey = verifyCodeEnum.getRedisKey() + accountModel.getSource() + ":"+ accountModel.getAccount();
                String getRedisKeyContent = redisUtil.get(redisKey);
                if (StringUtil.isEmpty(getRedisKeyContent)) {
                    log.warn("未找到{} 账户的注册验证码, 验证码失效或过期", accountModel.getAccount());
                    throw new BusinessException(ResultHintConstant.VERIFY_CODE_UNKNOWN);
                }
                if (!accountModel.getPwd().equals(getRedisKeyContent)) {
                    log.warn("错误验证码{}, 正确验证码{}", accountModel.getPwd(), getRedisKeyContent);
                    throw new BusinessException(ResultHintConstant.VERIFY_CODE_ERROR);
                }
                redisUtil.delete(redisKey);
            }
            default -> {
                String saltPwd = AuthUtils.md5Hex(accountModel.getPwd(), getUserModel.getSalt());
                if (!saltPwd.equals(getUserModel.getPwd())) {
                    log.warn("密码验证错误");
                    throw new BusinessException(ResultHintConstant.LOGIN_PWD_ERROR);
                }
            }
        }
    }
}
